const localVideo = document.querySelector('video#localvideo');
const remoteVideo = document.querySelector('video#remotevideo');
const btnConn = document.querySelector('button#connserver');
const btnLeave = document.querySelector('button#leave');

btnConn.onclick = connSignalServer;
btnLeave.onclick = leave;

function connSignalServer(){
  //开启本地视频采集
  start();

  return true;
}

function start(){
  console.log('navigator.mediaDevices:',navigator.mediaDevices);
  if(
    !navigator.mediaDevices
    || !navigator.mediaDevices.getUserMedia
  ){
    console.error('the getUserMedia is not supported!');
    return;
  }else {
    const constrains = {
      video: true,
      audio: false
    };

    navigator.mediaDevices.getUserMedia(constrains)
      .then(getMediaStream)
      .catch(handleError)
  }
}

function handleError(err){
  console.error('Failed to get Media Stream!', err);
}


let localStream = null; // 后面会往这个流里添加track

function getMediaStream(stream){
  localVideo.srcObject = stream;
  localStream = stream;

  conn();
}

let socket = null,
  state = 'init',
  roomId = '111111';

function conn(){
  socket = io.connect(); //等同于 io()

  socket.on('joined', (roomId, id) => {
    console.log('receive joined message:', roomId, id);
    state = 'joined';
    console.log(`state changed to 'joined'`);
    createPeerConnection();
    btnConn.disabled = true;
    btnLeave.disabled = false;
  });

  socket.on('otherjoin', (roomId, id) => {
    console.log('otherjoin================================================================================');
    console.log('receive otherjoin message:', roomId, id);

    if (state === 'joined_unbind'){ //说明之前已经连接过 并且对方已经退出 连接已经断开 我们要重新创建连接
      createPeerConnection();
    }
    state = 'joined_conn';
    console.log(`state changed to 'joined_conn'`);
    call(); // 因为我们可以断开（leave），然后再第二次Connect, 故把call放在 on otherjoin里, 这样每次有人进来, 就可以重新 createOffer 并把这个offer传递给新进来的那个人
  });

  socket.on('full', (roomId, id) => {
    console.log('receive full message:', roomId, id);
    state = 'leaved';
    console.log(`state changed to 'leaved'`);
    socket.disconnect();
    alert('the room is full!');

    btnConn.disabled = false;
    btnLeave.disabled = true;
  });

  socket.on('leaved', (roomId, id) => {
    console.log('receive leaved message:', roomId, id);
    state = 'leaved';
    console.log(`state changed to 'leaved'`);
    socket.disconnect();

    btnConn.disabled = false;
    btnLeave.disabled = true;
  });

  socket.on('bye', (roomId, id) => {
    console.log('receive bye message:', roomId, id);
    state = 'joined_unbind';
    closePeerConnection();
    console.log(`state changed to 'joined_unbind'`);
  });

  socket.on('message', (roomId, socketId, data) => {
    console.log('receive client message:', roomId, data);

    switch (data.type) {
      case 'offer':
        pc.setRemoteDescription(new RTCSessionDescription(data)); //信令发过来的时候 不是一个对象 而是一个字符串 （getOffer时是一个对象, sendMessage再发送过来的时候已经转化成文本了）, 故用 RTCSessionDescription 包裹
        pc.createAnswer()
          .then(getAnswer)
          .catch(handleAnswerError)
        break;

      case 'answer':
        pc.setRemoteDescription(new RTCSessionDescription(data))
        break;

      case 'candidate': //setLocalDescription之后就会开始搜集candidate, 每当搜集一个candidate之后, 实际会触发 pc.onicecandidate 这个事件, 在这个事件触发之后, 它会发送sendMessage, 发送过来之后呢 我们接受到这个candidate, 也要 new 一个 RTCIceCandidate
        const candidate = new RTCIceCandidate({
          sdpMLineIndex: data.label,
          candidate: data.candidate
        });
        pc.addIceCandidate(candidate);
        break;

      default:
        console.error('the message is invalid!', data);
        break;
    }
  });

  socket.emit('join', roomId); //todo 让用户输入一个房间号

  return;
}

function leave(){
  if(socket){
    socket.emit('leave', roomId); //真正断开(socket.disconnect)是在收到服务器返回 leaved 的时候
  }

  closePeerConnection();
  closeLocalMedia();

  btnConn.disabled = false;
  btnLeave.disabled = true;
}

let pc = null;

function createPeerConnection(){
  console.log('create RTCPeerConnection!');
  if (!pc) {
    const pcConfig = {
      iceServers:[
        {
          // 网友的提供的:
          // urls: 'turn:stun.al.learningrtc.cn:3478',
          // credential: 'mypasswd',
          // username: 'garrylea'
          // 更多: https://gist.github.com/yetithefoot/7592580

          // 谷歌提供的:
          urls: 'stun:stun.l.google.com:19302'
        }/*,
        {
          urls: 'turn:x.x.x:3478',
          username: 'xxx',
          credential: 'yyy'
        }*/
      ]
    };
    pc = new RTCPeerConnection(pcConfig);
    pc.onicecandidate = (e) => {
      if(e.candidate){
        console.log('pc.onicecandidate ---> find a new candidate:', e.candidate);
        /*
          RTCIceCandidate {candidate: "candidate:1351313377 1 udp 2122260223 172.30.48.1 …208 typ host generation 0 ufrag 3j/P network-id 1", sdpMid: "0", sdpMLineIndex: 0, foundation: "1351313377", component: "rtp", …}
            address: "172.30.48.1"
            candidate: "candidate:1351313377 1 udp 2122260223 172.30.48.1 51208 typ host generation 0 ufrag 3j/P network-id 1"
            component: "rtp"
            foundation: "1351313377"
            port: 51208
            priority: 2122260223
            protocol: "udp"
            relatedAddress: null
            relatedPort: null
            sdpMLineIndex: 0
            sdpMid: "0"
            tcpType: null
            type: "host"
            usernameFragment: "3j/P"
            [[Prototype]]: RTCIceCandidate
        */
        sendMessage(roomId, {
          type: 'candidate',

          //todo ↓ emmm 没必要 直接把整个 e.candidate传过去即可
          label: e.candidate.sdpMLineIndex, //todo //在SDP中 m= 的索引值 // 比如说我们在这个SDP中一共有两个,一个视频,一个音频,那么的视频是第一个,音频是第二个,那么引入音频的话,就是2,视频的就是1。
          id: e.candidate.sdpMid, //todo //sdpMid代表着每一路流,这个mid之前在讲SDP的时候给大家介绍过,比如说我们那个视频是video就是0
          candidate: e.candidate.candidate //看上面的注释 e.candidate里还有个candidate属性
        });
      }
    };

    pc.ontrack = (e)=>{
      remoteVideo.srcObject = e.streams[0]; //PeerConnection可以传多路流
    }
  }

  if(localStream){
    localStream.getTracks().forEach(track => {
      console.log('track:',track);
      pc.addTrack(track,localStream);
      // todo
      // 这个时候我们有音频有视频都加到PeerConnection之后呢
      // 当我们进行媒体协商的时候, 创建offer, 它里面的sdp就会产生音频的媒体流和视频的媒体流（如果只有音频就只会添加音频媒体流,只有视频就只会添加视频媒体流）
      // 这样双方才能知道对方有什么流, 才能进行通讯
      // 如果不加这个的话实际就不行, sdp没有表示这个媒体的话, 那传到对方, 对方其实就说你没有这个媒体, 它就不会做相应的操作
    });
  }
}

//点hangup后第二个video就黑屏了(因为连接销毁了)
//但第一个还在（因为第一个我们是getUserMedia使用摄像头捕获的视频流赋给的video1, 是一直在的）
//当我们再点击start 它就又会创建peerconnection进行连接, 第二个video就又亮起来了
function closePeerConnection(){
  console.log('close RTCPeerConnection~');
  if(pc){
    pc.close();
    pc = null;
  }
}

function closeLocalMedia(){
  if (localStream && localStream.getTracks()) {
    localStream.getTracks().forEach(track=>{
      track.stop();
    })
  }
  localStream = null;
}

const offer = document.querySelector('textarea#offer');
const answer = document.querySelector('textarea#answer');

function call(){
  console.log('call------------');
  if (state === 'joined_conn') {
    if(pc){
      const options = {
        offerToReceiveAudio: true,
        offerToReceiveVideo: true,
        voiceActivityDetection: true,
        iceRestart:true,
      };

      pc.createOffer(options)
        .then(getOffer)
        .catch(handleOfferError)
    }
  }
}

function getOffer(desc){
  console.log('getOffer');
  pc.setLocalDescription(desc); //此时底层就会调用ICE去搜集candidate

  sendMessage(roomId, desc); //这里创建的desc,即sdp,是自带一个type属性的, type='offer'
}

function sendMessage(roomId, data) {
  console.log('sendMessage:', roomId, data);
  if(socket){
    socket.emit('message', roomId, data);
  }
}

function handleOfferError(err){
  console.error('Failed to get Offer!', err);
}

function getAnswer(desc) {
  console.log('getAnswer');
  pc.setLocalDescription(desc);
  sendMessage(roomId, desc);
}

function handleAnswerError(err){
  console.error('Failed to get Answer!', err);
}
